package com.we.es.support;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.nutz.lang.Mirror;
import org.nutz.log.Log;
import org.nutz.log.Logs;

import com.google.common.collect.Lists;
import com.uxuexi.core.common.util.CollectionUtil;
import com.uxuexi.core.common.util.ExceptionUtil;
import com.uxuexi.core.common.util.Util;
import com.we.es.entity.EsIndexEntity;
import com.we.es.support.annotation.FacetConfig;
import com.we.es.support.annotation.IndexField;
import com.we.es.util.DateConvertUtil;
import com.we.es.util.ReflectionUtil;

/**
 * 提供在索引处理过程中可能需要用到的一些常用方法。
 * <p>
 * 主要是从实体的注解信息中提取出索引的个性化信息，为属性名添加指定的后缀，然后es根据预先定义的mapping去创建索引
 * @author   朱晓川
 */
public class IndexHelper {

	private static final Log log = Logs.get();

	/**索引实体id字段*/
	public static final String ID_FILED = "id";

	/**
	 * 分词，索引，存储
	 */
	public static final String TOKENIZED_IDX_STORE = "_tk_idx_st";

	/**
	 * 分词，索引，不存储
	 */
	public static final String TOKENIZED_IDX_NONESTORE = "_tk_idx_nt";

	/**
	 * 分词，不索引，存储
	 */
	public static final String TOKENIZED_NIDX_STORE = "_tk_nidx_st";

	/**
	 * 不分词,索引，存储
	 */
	public static final String UN_TOKENIZED_IDX_STORE = "_ntk_idx_st";

	/**
	 * 不分词,索引，不存储
	 */
	public static final String UN_TOKENIZED_IDX_NONESTORE = "_ntk_idx_nt";

	/**
	 * 不分词,不索引，存储
	 */
	public static final String UN_TOKENIZED_NIDX_STORE = "_ntk_nidx_st";

	//基本类型的后缀

	/**布尔类型*/
	public static final String BOOLEAN = "_bs";
	/**int类型*/
	public static final String INT = "_is";
	/**long类型*/
	public static final String LONG = "_ls";
	/**float类型*/
	public static final String FLOAT = "_fs";
	/**double类型*/
	public static final String DOUBLE = "_ds";
	/**date类型*/
	public static final String DATE = "_dts";

	/**
	 * 每种索引的默认类型:
	 * Elastic 6.x 版只允许每个 Index 包含一个 Type，7.x 版将会彻底移除 Type。
	 */
	public final static String DEFAULT_TYPE = "default";

	private IndexHelper() {
	}

	/**
	 * 从对象实体获取用于创建索引需要的scource数据，本质是一个json字符串
	 * <p>
	 * @param entity 对象实体
	 */
	public static XContentBuilder getScource(final EsIndexEntity entity) {
		Field[] fields = ReflectionUtil.getFields(entity);
		if (Util.isEmpty(fields)) {
			return null;
		}
		XContentBuilder cb = null;
		try {
			cb = XContentFactory.jsonBuilder().startObject();
			// 遍历
			for (Field f : fields) {
				String fn = f.getName(); // 字段名 

				Object val = ReflectionUtil.getFieldValue(entity, f);
				if (f.isAnnotationPresent(IndexField.class)) {
					fn = IndexHelper.getIndexFieldName(f);
				}
				cb.field(fn, val);
				log.debug("createIndex|" + fn + ":" + val);
			}
			cb.endObject();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return cb;
	}

	/**
	 * 获取实体所有index=true的字段名
	 */
	public static List<String> getIndexedFields(Class<?> entityClass) {
		ExceptionUtil.checkEmpty(entityClass, "索引实体类型不能为空");
		Mirror<?> mirror = Mirror.me(entityClass);

		// 获取entity类中的所有属性 
		Field[] fields = mirror.getFields();
		if (Util.isEmpty(fields))
			return null;

		List<String> result = Lists.newArrayList();
		for (Field field : fields) {
			//原名
			String fname = field.getName();

			Class<?> type = field.getType();
			if (String.class == type) {
				if (field.isAnnotationPresent(IndexField.class)) {
					IndexField indexField = field.getAnnotation(IndexField.class);
					if (indexField.index()) {
						//补后缀
						fname = getIndexFieldName(field);
						result.add(fname);
					}
				} else {
					//不存在注解
					result.add(fname);
				}
			} else {
				//TODO  如果待匹配的字段为数字只有当搜索关键字为数字的时候才能使用
			}
		}
		return result;
	}

	/**
	 * 获取实体所有index=true的字段名
	 */
	public static String[] getIndexedFieldsArr(Class<?> entityClass) {
		List<String> lst = getIndexedFields(entityClass);
		return toArray(lst);
	}

	/**
	 * 
	 * 使用反射将索引文档中的值设置到entity对象中
	 * <p>
	 * 实体对象必须继承自@see SolrIndexEntity
	 * 
	 * @param entity       实体对象
	 * @param hit 索引文档
	 */
	public static void setEntityValueFromDoc(Object entity, SearchHit hit) {
		Mirror<?> mirror = Mirror.me(entity.getClass());
		// 获取entity类中的所有属性 
		Field[] entityFields = mirror.getFields();

		//文档的source
		Map<String, Object> source = hit.getSourceAsMap();

		Map<String, HighlightField> hlFields = hit.getHighlightFields();

		for (Field field : entityFields) {
			final String fname = field.getName();
			String hitFieldName = null;
			if (ID_FILED.equals(fname)) {
				//设置id
				mirror.setValue(entity, fname, hit.getId());
			} else {
				//如果字段上不存在IndexField注解，则为普通字段，不会存在特殊后缀
				if (!field.isAnnotationPresent(IndexField.class)) {
					hitFieldName = fname;
				} else {
					hitFieldName = getIndexFieldName(field);
				}
				Object hitFieldVal = source.get(hitFieldName);
				//日期处理
				if (Date.class == field.getType() && !Util.isEmpty(hitFieldVal)) {
					log.debug("set Date value " + hitFieldName + ":" + hitFieldVal);
					hitFieldVal = DateConvertUtil.utc2Local(hitFieldVal.toString());
				}

				//高亮
				if (!Util.isEmpty(hlFields)) {
					HighlightField hf = hlFields.get(hitFieldName);
					if (!Util.isEmpty(hf)) {
						Text[] fragments = hf.getFragments();
						//有则设置
						if (!Util.isEmpty(fragments)) {
							hitFieldVal = fragments[0];
							log.debug("hf:" + hf.toString());
						}
					}
				}
				//设置值
				mirror.setValue(entity, fname, hitFieldVal);
			}
		}
	}

	/**
	 * 获取实体的分片字段
	 */
	public static List<WeFacetField> getFacetFields(Class<?> entityClass) {
		ExceptionUtil.checkEmpty(entityClass, "索引实体类型不能为空");

		List<WeFacetField> result = new ArrayList<WeFacetField>();
		Mirror<?> mirror = Mirror.me(entityClass);

		// 获取带有IndexField注解的字段
		Field[] fields = mirror.getFields(IndexField.class);
		if (Util.isEmpty(fields))
			return CollectionUtil.list();

		for (Field field : fields) {
			if (!field.isAnnotationPresent(FacetConfig.class)) {
				continue;
			}

			String fname = field.getName();
			FacetConfig facetConfig = field.getAnnotation(FacetConfig.class);

			WeFacetField wf = new WeFacetField();
			wf.setName(fname);
			wf.setFacetConfig(facetConfig);

			result.add(wf);
		}
		return result;
	}

	/**
	 * 获取实体的高亮字段
	 */
	public static String[] getHighLightFields(Class<?> entityClass) {
		ExceptionUtil.checkEmpty(entityClass, "索引实体类型不能为空");
		Mirror<?> mirror = Mirror.me(entityClass);

		// 获取entity类中的所有属性 
		Field[] fields = mirror.getFields();
		if (Util.isEmpty(fields))
			return null;

		List<String> lst = Lists.newArrayList();
		for (Field field : fields) {
			Class<?> type = field.getType();
			if (String.class == type) {
				//原名
				String fname = field.getName();
				if (field.isAnnotationPresent(IndexField.class)) {
					IndexField fd = field.getAnnotation(IndexField.class);
					if (fd.highLight()) {
						//补后缀
						fname = getIndexFieldName(field);
						lst.add(fname);
					}
				} else {
					//不存在注解
					lst.add(fname);
				}
			} else {
				//非字符串类型的字段就别考高亮了...TODO
			}
		}
		return toArray(lst);
	}

	/**
	 * 获取索引字段的名称（给属性名添加特定的后缀）
	 * <p>
	 * 对于一个普通的entity而言，它的某个属性是否分词、是否索引以及是否存储，
	 * 是使用特定的后缀mapping来匹配实现的，因此真正索引的字段名需要添加上特定的后缀.
	 *
	 * @param field  属性
	 * @return       带指定后缀的属性名
	*/
	public static String getIndexFieldName(final Field field) {
		IndexField inf = field.getAnnotation(IndexField.class);
		if (Util.isEmpty(inf)) {
			throw ExceptionUtil.pEx("该属性上不存在IndexField注解,请检查", inf);
		}
		return getIndexFieldName(field.getType(), field.getName(), inf);
	}

	/**
	 * 
	 * 获取索引字段的名称（给属性名添加特定的后缀）.
	 * 默认分词，索引并且存储
	 *
	 * @param name            属性名
	 * @param idxField       @see SolrField
	 * @return                带指定后缀的属性名
	 */
	public static String getIndexFieldName(Class<?> fieldType, final String name, IndexField idxField) {
		if (Util.isEmpty(idxField)) {
			return getIndexFieldName(fieldType, name, true, true, true);
		}
		return getIndexFieldName(fieldType, name, idxField.needAnalyse(), idxField.index(), idxField.store());
	}

	/**
	 * 
	 * 获取索引字段的名称（给属性名添加特定的后缀）
	 *
	 * @param name               属性名
	 * @param needAnalyse        是否分词
	 * @param indexed            是否索引
	 * @param stored             是否存储
	 * @return 带指定后缀的属性名
	 */
	private static String getIndexFieldName(Class<?> fieldType, final String name, final boolean needAnalyse,
			final boolean indexed, final boolean stored) {
		String result = "";
		if (boolean.class == fieldType) {
			result = name + BOOLEAN;
		} else if (int.class == fieldType) {
			result = name + INT;
		} else if (long.class == fieldType) {
			result = name + LONG;
		} else if (float.class == fieldType) {
			result = name + FLOAT;
		} else if (double.class == fieldType) {
			result = name + DOUBLE;
		} else if (Date.class == fieldType) {
			result = name + DATE;
		} else if (String.class == fieldType) {
			result = name + TOKENIZED_IDX_STORE;
			if (needAnalyse) {
				if (indexed && !stored) {
					result = name + TOKENIZED_IDX_NONESTORE;
				}
				if (!indexed && stored) {
					result = name + TOKENIZED_NIDX_STORE;
				}
			} else {
				if (indexed && stored) {
					result = name + UN_TOKENIZED_IDX_STORE;
				}
				if (indexed && !stored) {
					result = name + UN_TOKENIZED_IDX_NONESTORE;
				}
				if (!indexed && stored) {
					result = name + UN_TOKENIZED_NIDX_STORE;
				}
			}
		} else {
			throw ExceptionUtil.pEx("添加动态后缀失败,未知的字段类型。fieldType:" + fieldType + ",name :" + name + ".请使用Java基础数据类型");
		}
		return result;
	}

	public static String[] toArray(List<String> lst) {
		if (Util.isEmpty(lst)) {
			return null;
		} else {
			String[] sts = new String[lst.size()];
			return lst.toArray(sts);
		}
	}
}